const config = require('../config/ble.config');

const wxp = fn => (opts = {}) => new Promise((resolve, reject) => {
  fn({
    ...opts,
    success: resolve,
    fail: reject
  });
});

const delay = ms => new Promise(r => setTimeout(r, ms));

const ble = {
  adapterOpened: false,
  devices: new Map(),
  connectedDeviceId: null,
  notifyChar: null,
  writeChar: null,
  serviceId: null,

  async init() {
    if (this.adapterOpened) return;
    await wxp(wx.openBluetoothAdapter)();
    this.adapterOpened = true;
    wx.onBluetoothDeviceFound(res => {
      (res.devices || []).forEach(d => {
        if (!d.name && !d.localName) return;
        if (typeof config.deviceFilter === 'function' && !config.deviceFilter(d)) return;
        this.devices.set(d.deviceId, d);
      });
    });
    wx.onBLEConnectionStateChange(({ deviceId, connected }) => {
      if (!connected && this.connectedDeviceId === deviceId) {
        this.connectedDeviceId = null;
      }
    });
  },

  async startScan(timeoutMs = 8000) {
    await this.init();
    this.devices.clear();
    try {
      await wxp(wx.startBluetoothDevicesDiscovery)({ allowDuplicatesKey: false });
    } catch (e) {
      // 适配器在发现中时可能报错，先停止再启动
      try { await wxp(wx.stopBluetoothDevicesDiscovery)(); } catch (_) {}
      await wxp(wx.startBluetoothDevicesDiscovery)({ allowDuplicatesKey: false });
    }
    if (timeoutMs > 0) {
      await delay(timeoutMs);
      await this.stopScan();
    }
    return Array.from(this.devices.values());
  },

  async stopScan() {
    try { await wxp(wx.stopBluetoothDevicesDiscovery)(); } catch (_) {}
    return Array.from(this.devices.values());
  },

  async connect(deviceId) {
    await this.stopScan();
    await wxp(wx.createBLEConnection)({ deviceId, timeout: 15000 });
    this.connectedDeviceId = deviceId;
    const { services } = await wxp(wx.getBLEDeviceServices)({ deviceId });
    const targetService = (config.serviceUUID && services.find(s => s.uuid.toLowerCase() === config.serviceUUID.toLowerCase())) || null;
    const serviceList = targetService ? [targetService] : services;
    for (const s of serviceList) {
      const { characteristics } = await wxp(wx.getBLEDeviceCharacteristics)({ deviceId, serviceId: s.uuid });
      let write = null;
      let notify = null;
      if (config.writeCharacteristicUUID) {
        write = characteristics.find(c => c.uuid.toLowerCase() === config.writeCharacteristicUUID.toLowerCase());
      }
      if (config.notifyCharacteristicUUID) {
        notify = characteristics.find(c => c.uuid.toLowerCase() === config.notifyCharacteristicUUID.toLowerCase());
      }
      if (!write) write = characteristics.find(c => c.properties.write || c.properties.writeNoResponse);
      if (!notify) notify = characteristics.find(c => c.properties.notify || c.properties.indicate);
      if (write || notify) {
        this.serviceId = s.uuid;
        this.writeChar = write ? write.uuid : null;
        this.notifyChar = notify ? notify.uuid : null;
        if (this.notifyChar) {
          await wxp(wx.notifyBLECharacteristicValueChange)({
            deviceId,
            serviceId: s.uuid,
            characteristicId: this.notifyChar,
            state: true
          });
        }
        break;
      }
    }
    return { deviceId, serviceId: this.serviceId, writeChar: this.writeChar, notifyChar: this.notifyChar };
  },

  async disconnect() {
    if (!this.connectedDeviceId) return;
    try {
      await wxp(wx.closeBLEConnection)({ deviceId: this.connectedDeviceId });
    } finally {
      this.connectedDeviceId = null;
    }
  },

  onNotify(callback) {
    wx.onBLECharacteristicValueChange(res => {
      callback && callback(res);
    });
  },

  async writeHex(hexString) {
    if (!this.connectedDeviceId || !this.serviceId || !this.writeChar) throw new Error('No writable characteristic');
    const clean = hexString.replace(/\s+/g, '').toLowerCase();
    const typedArray = new Uint8Array(clean.length / 2);
    for (let i = 0; i < typedArray.length; i++) {
      typedArray[i] = parseInt(clean.substr(i * 2, 2), 16);
    }
    return wxp(wx.writeBLECharacteristicValue)({
      deviceId: this.connectedDeviceId,
      serviceId: this.serviceId,
      characteristicId: this.writeChar,
      value: typedArray.buffer
    });
  },

  async writeText(text) {
    const encoder = new TextEncoder();
    const bytes = encoder.encode(text);
    if (!this.connectedDeviceId || !this.serviceId || !this.writeChar) throw new Error('No writable characteristic');
    return wxp(wx.writeBLECharacteristicValue)({
      deviceId: this.connectedDeviceId,
      serviceId: this.serviceId,
      characteristicId: this.writeChar,
      value: bytes.buffer
    });
  }
};

module.exports = ble;


